'''
Created on 24 Oct 2012

@author: quirino.brizi
'''
from urllib import urlencode as _urlencode
import pycurl
from org.codesketch.loadit.decorator import accepts, selfType

try :
    from cStringIO import StringIO
except :
    from StringIO import StringIO

try:
    import signal
    from signal import SIGPIPE, SIG_IGN
    signal.signal(SIGPIPE, SIG_IGN)
except ImportError:
    pass

class WebClient():
    '''
    cURL based HTTP web client implementation.
    '''


    def __init__(self, baseUrl="", headers=[]):
        '''
        Create a new WebClient instance setting base configuration
        '''
        self.__handle = pycurl.Curl()
        self.__headers = []
        self.__headerChunks = []
        self.__bodyChunks = []
        
        self.setOption(pycurl.FOLLOWLOCATION, 1)
        self.setOption(pycurl.MAXREDIRS, 5)
        self.setOption(pycurl.NOSIGNAL, 1)
        self.setOption(pycurl.SSL_VERIFYHOST, 0)
        self.setOption(pycurl.SSL_VERIFYPEER, 0)
        self.setOption(pycurl.WRITEFUNCTION, self.__bodyProcessor)
        self.setOption(pycurl.HEADERFUNCTION, self.__headerProcessor)
        # just erase all the cookies known by cURL (From 7.14.1)
        self.setOption(pycurl.COOKIELIST, 'ALL')
        
        self.setHeaders(headers)
        self.setUrl(baseUrl)
        self.setVerbosity(0)
        self.setTimeout(30)
        
        self.__ready = False
    
    def __del__(self): self.close()
    
    @accepts(selfType, int, debug=2)
    def setTimeout(self, timeout): 
        '''
        Set the timeout in seconds for a receiving object
        '''
        self.setOption(pycurl.TIMEOUT, timeout)
    
    @accepts(selfType, int, debug=2)
    def setVerbosity(self, level): 
        '''
        Set the verbosity level, set to 1 to see cURL transactions
        '''
        self.setOption(pycurl.VERBOSE, level)
    
    @accepts(selfType, str, debug=2)
    def setUrl(self, url): 
        '''
        Set the base URL to be retrieved, relative path may or may not be set at request time.
        '''
        self.__baseUrl = url
        self.setOption(pycurl.URL, self.__baseUrl)
    def setOption(self, *args):
        '''
        Set an option on the retrieval
        '''
        apply(self.__handle.setopt, args)
    
    @accepts(selfType, list, debug=2)
    def setHeaders(self, headers=[]):
        '''
        Set HTTP request headers on the retrieval
        '''
        self.__headers.extend(headers);
        self.setOption(pycurl.HTTPHEADER, self.__headers)
    
    @accepts(selfType, str, str, debug=2)
    def setBasicAuthentication(self, username, password):
        '''
        Set username and password enabling HTTP request to be performed using 
        basic authentication
        '''
        self.setOption(pycurl.USERPWD, "%s:%s" % (username, password))
        
    def getInfo(self, *args):
        '''
        Get information about the HTTP transaction from retrieval
        '''
        return apply(self.__handle.getinfo, args);
    
    def isReady(self):
        '''
        Tells whether the HTTP call has been executed and the results are ready to be collected
        '''
        return self.__ready
    
    @accepts(selfType, str, dict, debug=1)
    def get(self, relativeUrl="", params=None):
        '''
        Execute HTTP GET request
        '''
        if params is not None :
            relativeUrl += "?" + _urlencode(params)
        self.setOption(pycurl.HTTPGET, 1)
        self.__request(relativeUrl);
        
    @accepts(selfType, str, list, str, debug=1)
    def post(self, relativeUrl="", params=None, payload=None):
        '''
        Execute HTTP POST request. The existence of params and payload parameters 
        is mutually exclusive.
        @param params: the post fields
        @param payload: the post payload
        '''
        if params is not None :
            self.setOption(pycurl.POSTFIELDS, _urlencode(params))
        elif payload is not None :
            self.setOption(pycurl.POSTFIELDS, payload)
        self.setOption(pycurl.POST, 1)
        self.__request(relativeUrl);
        
    @accepts(selfType, str, debug=2)
    def put(self, payload):
        '''
        Execute HTTP PUT method.
        @param payload: the payload to send to the remote endpoint
        '''
        self.setOption(pycurl.PUT, 1)
        self.setOption(pycurl.READFUNCTION, StringIO(payload).read)
        
    def close(self):
        '''
        Close a session freeing resources
        '''
        if self.__handle : self.__handle.close();
        self.__handle = None
#        del self.__bodyChunks
#        del self.__headerChunks
    
    def response(self):
        headers = self.__parseHeaders()
        cookies = self.__cookies()
        info = self.__info()
        return HttpResponse(''.join(self.__bodyChunks), headers, cookies, info)
    
    @accepts(selfType, str, debug=1)
    def __request(self, relativeUrl=None):
        '''
        Executes the HTTP request
        '''
        self.__ready = False
        if relativeUrl :
            self.setOption(pycurl.URL, "%s%s" % (self.__baseUrl, relativeUrl))
        try :
            self.__handle.perform()
        except pycurl.error, ex :
            if 23 == ex[0] :
                pass
            else :
                raise Exception(ex[0], ex[1])
        finally:
            self.__ready = True
        
    def __bodyProcessor(self, chunk):
        '''
        Process response body
        '''
        self.__bodyChunks.append(chunk)
        return len(chunk)
    def __headerProcessor(self, chunk):
        '''
        Process response header
        '''
        self.__headerChunks.append(chunk)
        return len(chunk)
    
    def __parseHeaders(self):
        '''
        Extract response headers
        '''
        headers = {}
        for header in self.__headerChunks :
            if not header.startswith('HTTP') :
                try :
                    name, value = header.split(': ', 1)
                    headers[name] = value
                except ValueError, _ : pass
        return headers
    
    def __cookies(self):
        '''
        Extract cookies
        '''
        cookies = {}
        for line in self.getInfo(pycurl.INFO_COOKIELIST) :
            chunks = line.split('\t')
            cookies[chunks[-2]] = chunks[-1]
        return cookies
    
    def __info(self):
        '''
        Return a map containing all the information about the latest HTTP transaction from the 
        retrieval
        '''
        m = {}
        m['effective-url'] = self.getInfo(pycurl.EFFECTIVE_URL)
        m['http-code'] = self.getInfo(pycurl.HTTP_CODE)
        m['total-time'] = self.getInfo(pycurl.TOTAL_TIME)
        m['namelookup-time'] = self.getInfo(pycurl.NAMELOOKUP_TIME)
        m['connect-time'] = self.getInfo(pycurl.CONNECT_TIME)
        m['pretransfer-time'] = self.getInfo(pycurl.PRETRANSFER_TIME)
        m['redirect-time'] = self.getInfo(pycurl.REDIRECT_TIME)
        m['redirect-count'] = self.getInfo(pycurl.REDIRECT_COUNT)
        m['size-upload'] = self.getInfo(pycurl.SIZE_UPLOAD)
        m['size-download'] = self.getInfo(pycurl.SIZE_DOWNLOAD)
        m['speed-upload'] = self.getInfo(pycurl.SPEED_UPLOAD)
        m['header-size'] = self.getInfo(pycurl.HEADER_SIZE)
        m['request-size'] = self.getInfo(pycurl.REQUEST_SIZE)
        m['content-length-download'] = self.getInfo(pycurl.CONTENT_LENGTH_DOWNLOAD)
        m['content-length-upload'] = self.getInfo(pycurl.CONTENT_LENGTH_UPLOAD)
        m['content-type'] = self.getInfo(pycurl.CONTENT_TYPE)
        m['response-code'] = self.getInfo(pycurl.RESPONSE_CODE)
        m['speed-download'] = self.getInfo(pycurl.SPEED_DOWNLOAD)
        m['ssl-verifyresult'] = self.getInfo(pycurl.SSL_VERIFYRESULT)
        m['filetime'] = self.getInfo(pycurl.INFO_FILETIME)
        m['starttransfer-time'] = self.getInfo(pycurl.STARTTRANSFER_TIME)
        m['http-connectcode'] = self.getInfo(pycurl.HTTP_CONNECTCODE)
        m['httpauth-avail'] = self.getInfo(pycurl.HTTPAUTH_AVAIL)
        m['proxyauth-avail'] = self.getInfo(pycurl.PROXYAUTH_AVAIL)
        m['os-errno'] = self.getInfo(pycurl.OS_ERRNO)
        m['num-connects'] = self.getInfo(pycurl.NUM_CONNECTS)
        m['ssl-engines'] = self.getInfo(pycurl.SSL_ENGINES)
        m['cookielist'] = self.getInfo(pycurl.INFO_COOKIELIST)
        m['lastsocket'] = self.getInfo(pycurl.LASTSOCKET)
        m['ftp-entry-path'] = self.getInfo(pycurl.FTP_ENTRY_PATH)
        return m

class HttpResponse :
    
    def __init__(self, body, headers, cookies, info):
        self.__body = body
        self.__parseHeaders = headers
        self.__cookies = cookies
        self.__info = info
    
    def body(self): return self.__body
    def headers(self): return self.__parseHeaders
    def cookies(self): return self.__cookies
    def info(self): return self.__info
